﻿module Password

open System
open System.Windows
open System.Windows.Controls
open System.Windows.Media
open System.Windows.Shapes

module PasswordGenerator =
    let rnd = new Random()
    /// Removes an element form a list.
    let remove n lst = lst |> List.filter (fun x -> x <> n)
 
    /// Randomly permutes the elements of a list.
    let rec scramble (lst : 'a list) = 
        [
            let x = lst.[rnd.Next(0, lst.Length - 1)]
            yield x
            let lst' = remove x lst
            if not lst'.IsEmpty then
                yield! scramble lst'
        ]
 
    /// Generates a new password of the specified length from the given list of characters.
    let genPass chars len =    
 
        let chars' = scramble chars
 
        let pass = 
            [|
                for x in 1 .. len do
                    let rand = rnd.Next(0, chars.Length)
                    yield chars'.[rand]
            |]
        pass
 
    // Characters to use.
    let alpha = ['a' .. 'z']
    let upper = ['A' .. 'Z']
    let digits = ['1' .. '9']
    let punct = ['~'; '`'; '!'; '@'; '#'; '$'; '%'; '^'; '&'; '*'; '('; ')'; '-'; '_'; '+'; '='; '\"']
  
    // Password checking
    let totalChars = 95
    let alphaLen = alpha.Length
    let upperLen = upper.Length
    let digitsLen = digits.Length
    let punctLen = punct.Length
    let otherChars = totalChars - (alphaLen + upperLen + digitsLen + punctLen)
 
    /// Evaluates the strength of a password.
    let evalPass pass = 
        let evalChar (c : char) =
            match c with
            | _ when alpha |> List.exists (fun x -> x = c) -> alphaLen
            | _ when upper |> List.exists (fun x -> x = c) -> upperLen
            | _ when digits |> List.exists (fun x -> x = c) -> digitsLen
            | _ when punct |> List.exists (fun x -> x = c) -> punctLen
            | _ -> otherChars
    
        let score = pass |> Array.sumBy evalChar
 
        let score' = (log <| float score) * (float pass.Length / log 2.) |> floor
 
        let evalScore (bits : float) =
            match bits with
            | _ when bits >= 128. -> "Very Strong"
            | _ when bits < 128. && bits >= 64. -> "Strong"
            | _ when bits < 64. && bits >= 56. -> "Medium"
            | _ -> "Weak"  
 
        evalScore score'

    module Wpf =
        let wnd = Application.LoadComponent(new System.Uri("/Password;component/wind.xaml", UriKind.Relative)) :?> Window //xaml |> XamlReader.Parse :?> Window
        let check1 = wnd.FindName("c1") :?> CheckBox
        let check2 = wnd.FindName("c2") :?> CheckBox
        let check3 = wnd.FindName("c3") :?> CheckBox
        let b = wnd.FindName("btn") :?> Button
        let txtBox = wnd.FindName("txt1") :?> TextBox
        let combo = wnd.FindName("combo") :?> ComboBox
        let rec1 = wnd.FindName("rec1") :?> Rectangle
        let rec2 = wnd.FindName("rec2") :?> Rectangle
        let rec3 = wnd.FindName("rec3") :?> Rectangle
        let rec4 = wnd.FindName("rec4") :?> Rectangle
        let l4 = wnd.FindName("l4") :?> Label
        combo.ItemsSource <- [6 .. 32]
        combo.SelectedItem <- box 14

        /// Button click event handler.
        let handleClick _ =
            let lst =
                [
                    yield! alpha // By default only lower case letters are included.
                    if check1.IsChecked.Value then
                        yield! upper
                                    
                    if check2.IsChecked.Value then
                        yield! digits
                                    
                    if check3.IsChecked.Value then
                        yield! punct
                ]
            let len = unbox combo.SelectedItem
            let pswd = genPass lst len
            let pswd' = String pswd
            txtBox.Clear()
            txtBox.AppendText pswd'
        
        /// Text changed event handler.
        let handleTextChanged _ = 
            let txt = txtBox.Text
            rec1.Fill <- (Brushes.White :> Brush)
            rec2.Fill <- (Brushes.White :> Brush)
            rec3.Fill <- (Brushes.White :> Brush)
            rec4.Fill <- (Brushes.White :> Brush)

            match txt with
            | "" -> l4.Visibility <- Visibility.Hidden
            | _ -> let stren = evalPass <| txt.ToCharArray()
                   match stren with
                   | "Weak" -> rec1.Fill <- (Brushes.Red :> Brush)
                               l4.Visibility <- Visibility.Visible
                               l4.Content <- "Weak"
                   | "Medium" -> rec1.Fill <- (Brushes.Orange :> Brush) 
                                 rec2.Fill <- (Brushes.Orange :> Brush)
                                 l4.Visibility <- Visibility.Visible
                                 l4.Content <- "Medium"
                   | "Strong" -> rec1.Fill <- (Brushes.Green :> Brush) 
                                 rec2.Fill <- (Brushes.Green :> Brush)
                                 rec3.Fill <- (Brushes.Green :> Brush)
                                 l4.Visibility <- Visibility.Visible
                                 l4.Content <- "Strong"
                   | _ -> rec1.Fill <- (Brushes.Blue :> Brush) 
                          rec2.Fill <- (Brushes.Blue :> Brush)
                          rec3.Fill <- (Brushes.Blue :> Brush)
                          rec4.Fill <- (Brushes.Blue :> Brush)
                          l4.Visibility <- Visibility.Visible
                          l4.Content <- "Very Strong"
        
        b.Click |> Event.add handleClick
        txtBox.TextChanged |> Event.add handleTextChanged
        
        let app = Application()

        [<STAThread>]
        wnd |> app.Run |> ignore